/**
 * Copyright 2019 吉鼎科技.

 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.web.task.support.scripting;

import cn.easyplatform.lang.Files;
import cn.easyplatform.web.contexts.Contexts;
import cn.easyplatform.web.task.EventSupport;
import cn.easyplatform.web.task.zkex.ListSupport;
import cn.easyplatform.web.task.OperableHandler;
import cn.easyplatform.web.task.support.ExpressionEngine;
import cn.easyplatform.web.task.zkex.list.PanelSupport;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.RhinoException;
import org.mozilla.javascript.Script;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zkoss.zk.ui.Component;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
 * @since 2.0.0 <br/>
 */
public class SimpleExpressionEngine implements ExpressionEngine {

    private final static Logger log = LoggerFactory.getLogger(SimpleExpressionEngine.class);

    private static String functions;

    static {
        //InputStream is = EventScriptEngine.class.getResourceAsStream("cmd0.so");
        functions = Files.read("support/lib/cmd0.so");
    }

    private RhinoScriptable scope;

    private Script scriptEval;

    private String script;

    private EventSupport main;

    private UtilsCmd cmd;

    public SimpleExpressionEngine(EventSupport main, String script) {
        this.script = script.trim();
        this.main = main;
    }

    @Override
    public void exec(Component anchor, Map<String, Component> comps, Map<String, Object> data) {
        if (log.isDebugEnabled())
            log.debug("eval expression:{}", script);
        Context cx = Context.enter();
        try {
            cx.setOptimizationLevel(-1);
            initScope(cx, anchor);
            cx.evaluateString(scope, functions, "", 1, null);
            if (data != null) {
                for (Map.Entry<String, Object> entry : data.entrySet()) {
                    if (entry.getKey().startsWith("$"))
                        scope.setVariable(entry.getKey(), entry.getValue());
                    else
                        scope.setVariable("$" + entry.getKey(),
                                entry.getValue());
                }
            }
            for (Map.Entry<String, Component> entry : comps.entrySet())
                scope.setVariable(entry.getKey(), entry.getValue());
            cx.evaluateString(scope, script, "", 1, null);
        } catch (RhinoException ex) {
            throw ScriptUtils.handleScriptException(ex);
        } finally {
            Context.exit();
            scope.clear();
            scope = null;
        }
    }

    private void initScope(Context cx, Component anchor) {
        Map<String, Object> map = new HashMap<String, Object>();
        scope = new RhinoScriptable(map, false);
        scope.initStandardObjects(cx, false);
        cmd = new UtilsCmd(main, map);
        cmd.setAnchor(anchor);
        scope.setVariable("$", cmd);
        OperableHandler parent = null;
        if (main instanceof ListSupport)
            parent = ((ListSupport) main).getMainHandler().getParent();
        else if (main instanceof PanelSupport)
            parent = ((PanelSupport) main).getParent();
        if (parent != null) {
            scope.setVariable("$801", parent.getTaskId());
            scope.setVariable("$802", parent.getName());
            scope.setVariable("$814",
                    parent.getProcessCode());
            map.put("parent", parent);
            if (parent.getManagedComponents() != null) {
                Set<Map.Entry<String, Component>> set = parent.getManagedComponents().entrySet();
                for (Map.Entry<String, Component> entry : set)
                    map.put("parent$" + entry.getKey(), entry.getValue());
            }
        }
        scope.setVariable("$728", Contexts.getEnv().getDeviceType());
    }

    @Override
    public void compile() {
        if (log.isDebugEnabled())
            log.debug("eval expression:{}", script);
        Context cx = Context.enter();
        try {
            cx.setOptimizationLevel(9);
            initScope(cx, null);
            StringBuilder sb = new StringBuilder();
            sb.append(functions).append("\n").append(script);
            scriptEval = cx.compileString(sb.toString(), "", 1, null);
        } catch (RhinoException ex) {
            throw ScriptUtils.handleScriptException(ex);
        } finally {
            Context.exit();
        }
    }

    @Override
    public void eval(Component anchor, Map<String, Object> data, Map<String, Component> comps) {
        for (Map.Entry<String, Object> entry : data.entrySet()) {
            if (entry.getKey().startsWith("$"))
                scope.setVariable(entry.getKey(), entry.getValue());
            else
                scope.setVariable("$" + entry.getKey(), entry.getValue());
        }
        for (Map.Entry<String, Component> entry : comps.entrySet())
            scope.setVariable(entry.getKey(), entry.getValue());
        Context cx = Context.enter();
        try {
            cx.setOptimizationLevel(9);
            ((UtilsCmd) scope.get("$")).setAnchor(anchor);
            scriptEval.exec(cx, scope);
        } catch (RhinoException ex) {
            throw ScriptUtils.handleScriptException(ex);
        } finally {
            Context.exit();
            for (String name : data.keySet()) {
                if (name.startsWith("$"))
                    scope.delete(name);
                else
                    scope.delete("$" + name);
            }
            for (String name : comps.keySet())
                scope.delete(name);
        }
    }

    @Override
    public void destroy() {
        if (scope != null) {
            scope.clear();
            scope = null;
            scriptEval = null;
        }
    }
}
